package org.my.workflow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import org.my.workflow.element.ActionElement;
import org.my.workflow.element.ConditionElement;
import org.my.workflow.element.InterceptorElement;
import org.my.workflow.element.StepElement;
import org.my.workflow.element.TargetStepElement;
import org.my.workflow.persistence.StepEntity;
import org.my.workflow.persistence.StepStatus;
import org.my.workflow.persistence.WorkflowEntity;
import org.my.workflow.persistence.WorkflowStatus;

/**
 * @author <a href="mailto:yang_y@sysnet.com.cn">Young Yang</a>
 */
public class Workflow {

    private String id;

    private String owner = null;


    private WorkflowContext wfContext;

    private WorkflowConfig wfConfig;

    /**
     * 流程的载体，比如一张单据
     */
    private Payload payload;

    private WorkflowEntity wfEntity = null;

    public Workflow(WorkflowConfig wfConfig, String owner, Payload payload) {
        this.id = UUID.randomUUID().toString();
        this.wfConfig = wfConfig;
        this.owner = owner;
        this.payload = payload;
        this.wfContext = new DefaultWorkflowContext();
        initialize();
    }

    WorkflowConfig getWorkflowConfig() {
        return wfConfig;
    }

    WorkflowPersistence getWorkflowPersistence() {
        return wfConfig.getPersistence();
    }

    void initialize() {
        wfEntity = getWorkflowPersistence().newWorkflowEntity(getId());
        wfEntity.setName(getWorkflowConfig().getWorkflowElement().getName());
        wfEntity.setStatus(WorkflowStatus.CREATED);
        doAction(getWorkflowConfig().getWorkflowElement().getInitialActionElement());
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return getWorkflowConfig().getWorkflowElement().getName();
    }

    public WorkflowEntity getWorkflowEntity() {
        return wfEntity;
    }

    public WorkflowStatus getStatus() {
        return getWorkflowEntity().getStatus();
    }

    public boolean isCompleted() {
        return getStatus().equals(WorkflowStatus.COMPLETED);
    }

    public Payload getPayload() {
        return payload;
    }

    public String getOwner() {
        return owner;
    }

    WorkflowContext getWorkflowContext() {
        return wfContext;
    }

    public String[] getCurrentStepIds() {
        Collection<StepEntity> currentStepEntities = getWorkflowPersistence().getCurrentSteps(getId());
        List<String> stepIds = new ArrayList<String>(currentStepEntities.size());
        for (StepEntity stepEntity : currentStepEntities) {
            stepIds.add(stepEntity.getId());
        }
        return stepIds.toArray(new String[stepIds.size()]);
    }

    public String[] getCurrentStepElementIds() {
        Collection<StepEntity> currentStepEntities = getWorkflowPersistence().getCurrentSteps(getId());
        List<String> stepElementIds = new ArrayList<String>(currentStepEntities.size());
        for (StepEntity stepEntity : currentStepEntities) {
            StepElement stepElement = getWorkflowConfig().getStep(stepEntity.getStepElementId());
            stepElementIds.add(stepElement.getId());
        }
        return stepElementIds.toArray(new String[stepElementIds.size()]);
    }

    public List<StepElement> getCurrentStepElements() {
        List<StepElement> currentStepElements = new ArrayList<StepElement>(getCurrentStepElementIds().length);
        for (String stepElementId : getCurrentStepElementIds()) {
            currentStepElements.add(getWorkflowConfig().getStep(stepElementId));
        }
        return Collections.unmodifiableList(currentStepElements);
    }

    public Collection<StepEntity> getCurrentStepEntities() {
        return getWorkflowPersistence().getCurrentSteps(getId());
    }

    public StepElement[] getHistoryStepElements() {
        return null;
    }

    public List<StepEntity> getHistoryStepEntities() {
        return getWorkflowPersistence().getHistorySteps(getId());
    }

    public void persist() {

    }

    /**
     * 判断Action的Condition条件，仅返回符合条件的ActionElement列表
     */
    public List<ActionElement> getAvailableActionElements() {
        List<StepElement> currentStepElements = getCurrentStepElements();
        List<ActionElement> allAvailableActionElements = new ArrayList<ActionElement>();
        for (StepElement stepElement : currentStepElements) {

            ActionElement[] stepActionElements = stepElement.getActionElements();
            for (ActionElement actionElement : stepActionElements) {
                List<ConditionElement> actionConditionElements = actionElement.getConditionElements();
                boolean actionAvailable = true;
                for (ConditionElement conditionElement : actionConditionElements) {
                    if (!conditionElement.getCondition().isAllowed(getWorkflowContext(), conditionElement)) {
                        actionAvailable = false;
                        break;
                    }
                }
                if (actionAvailable) {
                    allAvailableActionElements.add(actionElement);
                }
            }

        }
        return allAvailableActionElements;
    }

    /**
     * 根据给定的stepElementId判断Action的Condition条件，仅返回符合条件的ActionElement列表
     *
     * @param stepElementId step element id
     */
    public List<ActionElement> getAvailableActionElements(String stepElementId) {
        List<StepElement> currentStepElements = getCurrentStepElements();
        StepElement currentStepElement = null;
        for (StepElement stepElement : currentStepElements) {
            if (stepElement.getId().equals(stepElementId)) {
                currentStepElement = stepElement;
                break;
            }
        }
        if (currentStepElement == null) {
            throw new WorkflowException("Step: " + stepElementId + " is not current step for workflow " + getId());
        }

        List<ActionElement> allAvailableActionElements = new ArrayList<ActionElement>();
        ActionElement[] stepActionElements = currentStepElement.getActionElements();
        for (ActionElement actionElement : stepActionElements) {
            List<ConditionElement> actionConditionElements = actionElement.getConditionElements();
            boolean available = true;
            for (ConditionElement conditionElement : actionConditionElements) {
                if (!conditionElement.getCondition().isAllowed(getWorkflowContext(), conditionElement)) {
                    available = false;
                    break;
                }
            }
            if (available) {
                allAvailableActionElements.add(actionElement);
            }
        }

        return allAvailableActionElements;
    }


    public void doAction(String actionId) {
        if (isCompleted()) {
            throw new WorkflowStatusException(getStatus(), "Can not do Action: " + actionId);
        }
        //仅返回符合Contidition条件的Action
        List<ActionElement> availableActionElements = getAvailableActionElements();
        ActionElement actionElement = null;
        for (ActionElement _actionElement : availableActionElements) {
            if (_actionElement.getId().equals(actionId)) {
                actionElement = _actionElement;
                break;
            }
        }

        if (actionElement == null) {
            throw new WorkflowException("Can not execute action: " + actionId + ", because action element is null.");
        }

        doAction(actionElement);

    }

    /**
     * execute Action
     *
     * @param actionElement action element
     */
    protected void doAction(ActionElement actionElement) {

        // don't need check conditions
/*
        List<ConditionElement> conditionElements = actionElement.getConditionElements();
        for (ConditionElement conditionElement : conditionElements) {
            if (!conditionElement.getCondition().isAllowed(getWorkflowContext(), conditionElement)) {
                throw new ConditionNotAllowedException(conditionElement.toString());
            }
        }
*/

        //do pre interceptors
        List<InterceptorElement> preInterceptorElements = actionElement.getPreInterceptorElements();
        for (InterceptorElement interceptorElement : preInterceptorElements) {
            interceptorElement.getInterceptor().doIntercept(getWorkflowContext(), interceptorElement);
        }

        // transition workflow status
        List<TargetStepElement> targetStepElements = getTargetElement(actionElement);
        String currentStepElementId = actionElement.getStepElementId();
        Collection<StepEntity> stepEntities = getWorkflowPersistence().nextSteps(getWorkflowContext(), currentStepElementId, targetStepElements.toArray(new TargetStepElement[targetStepElements.size()]));

        //do post interceptors
        List<InterceptorElement> postInterceptorElements = actionElement.getPostInterceptorElements();
        for (InterceptorElement interceptorElement : postInterceptorElements) {
            interceptorElement.getInterceptor().doIntercept(getWorkflowContext(), interceptorElement);
        }

    }

    protected List<TargetStepElement> getTargetElement(ActionElement actionElement) {
        List<TargetStepElement> targetStepElements = new ArrayList<TargetStepElement>(2);
        List<TargetStepElement> conditionalTargetStepElements = actionElement.getConditionalTargetStepElements();
        for (TargetStepElement _targetStepElement : conditionalTargetStepElements) {
            List<ConditionElement> _conditionElements = _targetStepElement.getConditionElements();
            boolean passed = true;
            for (ConditionElement conditionElement : _conditionElements) {
                boolean isAllowed = conditionElement.getCondition().isAllowed(getWorkflowContext(), conditionElement);
                if (!isAllowed) {
                    passed = false;
                    break;
                }
            }
            if (passed) {
                targetStepElements.add(_targetStepElement);
            }
        }
        if (targetStepElements.isEmpty()) {
            targetStepElements.add(actionElement.getUnconditionalTargetStepElement());
        }
        if (targetStepElements.isEmpty()) {
            throw new WorkflowException("No valid step for action: " + actionElement);
        }
        return targetStepElements;
    }

    class DefaultWorkflowContext implements WorkflowContext {

        public String getWorkflowId() {
            return getId();
        }

        public String getCaller() {
            return Workflow.this.getOwner();
        }

        public StepElement getStepElement(String stepElementId) {
            return getWorkflowConfig().getWorkflowElement().getStepElementById(stepElementId);
        }

        public List<StepElement> getCurrentStepElement() {
            return Workflow.this.getCurrentStepElements();
        }

        public Collection<StepEntity> getCurrentStepEntities() {
            return Workflow.this.getCurrentStepEntities();
        }

        public WorkflowStatus getStatus() {
            return Workflow.this.getStatus();
        }

        public List<StepEntity> getHistoryStepEntities() {
            return Workflow.this.getHistoryStepEntities();
        }

    }
}
